package chalmers.game.subterranean.model.world;

import chalmers.game.subterranean.event.Bus;
import chalmers.game.subterranean.event.events.world.AddActionBlockEvent;
import chalmers.game.subterranean.event.events.world.DamageBlockEvent;
import chalmers.game.subterranean.event.events.world.DestroyBlockEvent;
import chalmers.game.subterranean.model.world.squares.ISquare;
import chalmers.game.subterranean.model.world.squares.Square;
import chalmers.game.subterranean.model.world.squares.blocks.actionblocks.Ladder;
import chalmers.game.subterranean.util.BlockType;
import chalmers.game.subterranean.util.BlockType.Action;
import chalmers.game.subterranean.util.BlockType.Nothing;
import chalmers.game.subterranean.util.Direction;
import chalmers.game.subterranean.util.Position;
import chalmers.game.subterranean.util.Resource;
import chalmers.game.subterranean.util.options.WorldValues;
import org.apache.log4j.Logger;

/**
 * @author Mikael Friederici
 */
public abstract class DepthLevel {

  protected ISquare[][] allSquares;
  private static Logger logger = Logger.getLogger("subLog");
  protected GameWorld world;

  public DepthLevel(GameWorld world) {
    this.world = world;
    this.allSquares = new Square[WorldValues.LEVEL_HEIGHT][WorldValues.LEVEL_WIDTH];
  }

  public abstract void generate();

  /**
   * 
   * @param position
   * @param direction
   * @return neighbour's position in desired direction if it's possible to move there, otherwise the
   *         same position will be returned.
   */
  protected Position getPoint(Position position, Direction direction) {
    Position neighbour = getNeighbour(position, direction);
    if (!valid(position)) {
      return position;
    }

    if (collisionDetected(position, neighbour)) {
      // TODO Ugly hack to filter out gravity spam messages.
      if (!Thread.currentThread().getName().equals("Timer-0"))
        logger.info("Collision " + direction.toString());
      return position;
    }
    // If we move to the next to last level, generate a new one.
    if (neighbour.depth == world.getNumOfLevels() - 2
        && neighbour.depth <= WorldValues.MAX_NUM_LEVELS - 2)
      world.generateNewDirtLevel();
    return neighbour;
  }

    protected void performUse(Position position) {
      int y = position.point.y;
      int x = position.point.x;
      allSquares[y][x].action();
  }

  protected BlockType getBlockType(Position position) {
    int x, y;
    if (!valid(position)) {
      return new BlockType(Nothing.NULL);
    }
    y = position.point.y;
    x = position.point.x;
    return allSquares[y][x].getType();
  }

  protected boolean addLadderBlock(Position position) {
      Position.Point p = position.point;
    if (allSquares[p.y][p.x].pressAgainst()) {
      allSquares[p.y][p.x] = new Square(new Ladder());
      Bus.INSTANCE.post(new AddActionBlockEvent(position,new BlockType(Action.LADDER)));
      return true;
    }
    return false;
  }

  protected boolean addActionBlock(Position position, Square actionBlock){
    int y = position.point.y;
    int x = position.point.x;

    if (allSquares[y][x].pressAgainst()) {
      allSquares[y][x] = actionBlock;
      Bus.INSTANCE.post(new AddActionBlockEvent(position,actionBlock.getType()));
      return true;
    }
    return false;
  }

  /**
   * Validate the position.
   * 
   * @param position
   * @return true if it's within game world boundaries, otherwise false.
   */
  private boolean valid(Position position) {
    int x = position.point.x, y = position.point.y, d = position.depth;
    int levelHeight = allSquares.length - 1;
    int levelWidth = allSquares[0].length - 1;
    if ((x >= 0 && x <= levelWidth) && (y >= 0 && y <= levelHeight)
        && (d >= 0 && d < world.getNumOfLevels())) {
      return true;
    }
    logger.error("Position: " + position.toString() + "located outside of game world!");
    return false;
  }

  /**
   * Check for collision with neighbour square.
   * 
   * @param position
   * @param neighbour
   * @return true if collision, otherwise false.
   */
  private boolean collisionDetected(Position position, Position neighbour) {
    int x = neighbour.point.x, y = neighbour.point.y;
    if (neighbour.depth != position.depth) {
      DepthLevel neighbourLevel = world.getDepthLevel(neighbour.depth);
      return !neighbourLevel.allSquares[y][x].pressAgainst();
    } else {
      return !allSquares[y][x].pressAgainst();
    }
  }

  /**
   * Retrieve neighbour block's position, if there are any.
   * 
   * @param position
   * @param direction
   * @return position of neighbour block if there are any, otherwise same position will be returned.
   */
  private Position getNeighbour(Position position, Direction direction) {
    int x = position.point.x;
    int y = position.point.y;
    int depth = position.depth;
    int levelHeight = allSquares.length - 1;
    int levelWidth = allSquares[0].length - 1;

    switch (direction) {
      case WEST:
        if (x > 0) {
          x--;
        }
        break;
      case EAST:
        if (x < levelWidth) {
          x++;
        }
        break;
      case SOUTH:
        if (y == levelHeight) {
          if (depth != world.getNumOfLevels() - 1) {
            return new Position(depth + 1, x, 0);
          }
        } else {
          y++;
        }
        break;
      case NORTH:
        if (y == 0) {
          if (depth != (world.getNumOfLevels()) && depth != 0) {
            return new Position(depth - 1, x, levelHeight);
          }
        } else if (y > 0) {
          y--;
        }
        break;
    }
    return new Position(depth, x, y);
  }


  abstract ISquare[][] getBlocks();

  /**
   * Try to dig block in desired direction.
   * 
   * @param direction
   * @param position
   * @return Resource the blocks hold.
   */
  protected Resource dig(Direction direction, Position position, double damage) {
    Resource theResource = Resource.NOTHING;

    if (!valid(position)) {
      logger.error("dig(): position outside level!");
      return theResource;
    }

    Position neighbour = getNeighbour(position, direction);
    if (neighbour.equals(position)) {
      logger.info("Dig: " + neighbour.toString() + " - no neighbour block to the "
          + direction.toString());
      return theResource;
    }
    theResource = tryDig(position, neighbour, damage);

    if (theResource == Resource.NOTHING) {
      Bus.INSTANCE.post(new DamageBlockEvent(neighbour));
    } else {
      Bus.INSTANCE.post(new DestroyBlockEvent(neighbour));
    }
    return theResource;
  }

  /**
   * Try to dig neighbour block in the correct level.
   * 
   * @param position
   * @param neighbour
   * @param damage
   * @return ResourceItem
   * @throws Exception
   */
  private Resource tryDig(Position position, Position neighbour, double damage) {
    int x = neighbour.point.x, y = neighbour.point.y;
    Resource theResource;

    if (neighbour.depth != position.depth) {
      DepthLevel neighbourLevel = world.getDepthLevel(neighbour.depth);
      theResource = neighbourLevel.allSquares[y][x].dig(damage);
    } else {
      theResource = allSquares[y][x].dig(damage);
    }
    return theResource;
  }

  public BlockType[][] getLevelBlocks() {
    BlockType[][] blocks = new BlockType[WorldValues.LEVEL_HEIGHT][WorldValues.LEVEL_WIDTH];
    for (int y = 0; y < allSquares.length; y++) {
      for (int x = 0; x < allSquares[y].length; x++) {
        blocks[y][x] = allSquares[y][x].getType();
      }
    }
    return blocks;
  }

  @Override
  /**
   * Dump entire level to string representation.
   */
  public String toString() {
    StringBuffer buffer = new StringBuffer();
    for (int y = 0; y < allSquares.length; y++) {
      buffer.append("|");
      for (int x = 0; x < allSquares[y].length; x++) {
        buffer.append(allSquares[y][x].toString());
      }
      buffer.append("|");
      buffer.append("\n");
    }
    return new String(buffer);
  }
}
